/*
 * AudioPlayer.java
 *
 * Created on 2007��3��19��, ����1:49
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.zxh.audio;

/**
 *
 * @author ��˶
 */
import javax.sound.sampled.*;
import java.io.*;
import java.net.*;


public class AudioPlayer implements IAudioPlayer
{
    /** The AudioInputStream used for retrieving audio */
    AudioInputStream currentSound = null;

    /** The clip (Line subinterface) used for audio playback.  */
    Clip clip = null;

    /** The current pan */
    float pan = 0.0f;

    /** The current gain */
    float gain = 0.0f;

    /** The gainControl retrieved from the playback line */
    FloatControl gainControl = null;

    /** The panControl retrieved from the playback line*/
    FloatControl panControl = null;

    /** The DataLine.Info used to retrieve the playback Line */
    DataLine.Info dlInfo = null;

    /** A reference to how the audio was loaded: InputStream, URL or File */
    Object loadReference;

    /** The current AudioFormat of the playback line */
    AudioFormat format;


    /**
     * Default constructor
     */
    public AudioPlayer()
    {
    }

    /**
     * Load the audio
     * @param url the Url to load the audio from
     * @throws Exception
     */
    public void loadAudio(URL url) throws Exception
    {
        loadReference = url; //cache the load reference
        currentSound = AudioSystem.getAudioInputStream(url); //retrieve the AudioInputStream
        finishLoadingAudio(); //complete the audio loading process
    }

    /**
     * Load the audio
     * @param file The file to load the audio from
     * @throws Exception
     */
    public void loadAudio(File file) throws Exception
    {
        loadReference = file; //cache the load refence
        currentSound = AudioSystem.getAudioInputStream(file); //retieve the AudioInputStream
        finishLoadingAudio(); //complete the audio loading process
    }

    /**
     * Load the audio
     * @param iStream
     * @throws Exception
     */
    public void loadAudio(InputStream iStream) throws Exception
    {
        loadReference = iStream; //cache the load reference

        //check to see whether the stream is an AudioInputStream
        if (iStream instanceof AudioInputStream){
            //if stream is an AudioInputStream
            //simply cast the InputStream to an AudioInputstream
            currentSound = (AudioInputStream)iStream;
        } else {
            //if stream is NOT an AudioInputStream
            //retrieve the AudioInputstream from the AudioSystem
            currentSound = AudioSystem.getAudioInputStream(iStream);
        }
        finishLoadingAudio(); //complete the audio loading process
    }

    /**
     * This method is caled by all of the loadAudio methods to avoid duplicate code
     * @throws Exception
     */
    protected void finishLoadingAudio() throws Exception
    {
        //cache the audio format
        format = currentSound.getFormat();

        //create a DataLine.Info with the current format settings from the loaded audio
        dlInfo = new DataLine.Info(
                Clip.class, format, ((int) currentSound.getFrameLength() * format.getFrameSize()));
        //request the Clip form the AudioSystem
        clip = (Clip) AudioSystem.getLine(dlInfo);
        //open the Clip
        clip.open(currentSound);
        //add a line listsner...
        clip.addLineListener(
                new LineListener() {
                    public void update(LineEvent event) {
                        if (event.getType().equals(LineEvent.Type.STOP)){
                            //if the line stops playback on it its own, reset to the beginning
                            if (clip != null){
                                clip.stop();
                                if (clip.getFramePosition() == clip.getFrameLength()){
                                    clip.setFramePosition(0);
                                }
                            }
                        }
                    }
                }
        );
        resetGain(); //set the gain of the playback line to the cached gain
//        resetPan();  //set the pan of the playback line to the cached pan

    }

    /**
     * Dump the audio
     */
    public void unloadAudio(){
        try {
            if (clip != null)
                clip.close();
            if (currentSound != null)
                currentSound.close();
            loadReference = null;
        } catch (Exception e){
            System.out.println("An exception occurred while unloading audio: " + e);
            e.printStackTrace();
        }

        currentSound = null;
        clip = null;
        gainControl = null;
        panControl = null;
        dlInfo = null;
        loadReference = null;
    }

    /////////////////////////////////////////////////////////////////////////////
    // T R A N S P O R T    C O N T R O L S
    /////////////////////////////////////////////////////////////////////////////

    public void play()
    {
        clip.start();
    }

    public void pause()
    {
        //stop without changing the playback position
        clip.stop();
    }

    public void stop()
    {
        //stop AND reset the playback position to the beginning
        clip.stop();
        clip.setFramePosition(0);
    }

    /**
     *  Whooooosh!
     */
    public void flush()
    {
        clip.close();
    }

    /////////////////////////////////////////////////////////////////////////////
    // U T I L I T Y   M E T H O D S
    /////////////////////////////////////////////////////////////////////////////

    public void setGain(float dB)
    {
        gain = dB;
        resetGain();
    }

    /**
     * Calls setGain with the current cached gain
     */
    protected void resetGain()
    {
        gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
        gainControl.setValue(this.gain);
    }

    public void setPan(float pan)
    {
        this.pan = pan;
        resetPan();
    }

    /**
     * Calls set pan with the current cached pan
     */
    protected void resetPan()
    {
        panControl = (FloatControl) clip.getControl(FloatControl.Type.PAN);
        panControl.setValue(this.pan);
    }

    public int getFramePosition(){
        try {
            return clip.getFramePosition();
        } catch (Exception e) {
            return -1;
        }
    }

    /**
     * @return The Line used for playback
     */
    public Line getLine(){
        return clip;
    }

    public AudioFormat getCurrentFormat(){
        return format;
    }

    /**
     * @return A new AudioInputStream used for displaying the Audio.
     */
    public AudioInputStream getAudioInputStream(){
        try {
            AudioInputStream aiStream;

            //check the load reference and retrieve a NEW AudioInputStream accordingly...
            if (loadReference == null){
                return null;
            } else if (loadReference instanceof URL) {
                URL url = (URL)loadReference;
                aiStream = AudioSystem.getAudioInputStream(url);
            } else if (loadReference instanceof File) {
                File file = (File)loadReference;
                aiStream = AudioSystem.getAudioInputStream(file);
            } else if (loadReference instanceof AudioInputStream){
                AudioInputStream stream = (AudioInputStream)loadReference;
                aiStream = AudioSystem.getAudioInputStream(stream.getFormat(), stream);
                stream.reset();
            } else {
                //must be AudioInputStream
                InputStream inputStream = (InputStream)loadReference;
                aiStream = AudioSystem.getAudioInputStream(inputStream);
            }

            return aiStream;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @return Whether there is any audio loaded
     */
    public boolean isAudioLoaded(){
        return loadReference!=null;
    }

    /**
     * @return The Clip (Line) used for playback
     */
    public Clip getClip() {
        return clip;
    }
}
